"""Python. Programowanie funkcyjne, wydanie 3

Rozdział 3., Zbiór przykładów 6
"""
# pylint: disable=line-too-long,wrong-import-position,reimported

from collections.abc import Callable

Extractor = Callable[[tuple[int, int, int, str]], int]

red: Extractor = lambda color: color[0]

green: Extractor = lambda color: color[1]

blue: Extractor = lambda color: color[2]

REPL_rgb_functions = """
>>> color = (203, 65, 84, "Ceglana czerwień")
>>> red(color)
203
>>> green(color)
65
>>> blue(color)
84
"""


from collections.abc import Callable

RGB = tuple[int, int, int, str]

redt: Callable[[RGB], int] = lambda color: color[0]

REPL_rgb_typed_functions = """
>>> color = (203, 65, 84, "Ceglana czerwień")
>>> redt(color)
203
"""

REPL_rgb_functions_3 = """
>>> from collections.abc import Callable
>>> RGB = tuple[int, int, int]
>>> red: Callable[[RGB], int] = lambda color: color[0]
>>> green: Callable[[RGB], int] = lambda color: color[1]
>>> blue: Callable[[RGB], int] = lambda color: color[2]
>>> almond = (239, 222, 205)
>>> red(almond)
239
"""
# from collections import namedtuple
# Color = namedtuple("Color", ("red", "green", "blue", "name"))

from typing import NamedTuple


class Color(NamedTuple):
    """Kolor RGB."""

    red: int
    green: int
    blue: int
    name: str


# from typing import NamedTuple
# class Color(NamedTuple):
#     """An RGB color."""
#     red: int
#     green: int
#     blue: int
#     name: str

REPL_color_tuple = """
>>> color = Color(203, 65, 84, "Ceglana czerwień")
>>> color.red
203
>>> color.green
65
>>> color.blue
84
"""

REPL_palette = """
>>> palette = [
...     Color(red=239, green=222, blue=205, name='Migdałowy'),
...     Color(red=205, green=149, blue=117, name='Antyczny mosiądz'),
...     Color(red=253, green=217, blue=181, name='Morela'),
...     Color(red=197, green=227, blue=132, name='Żółtozielony'),
...     Color(red=255, green=174, blue=66, name='Żółtopomarańczowy')
... ]

>>> name_map = dict((c.name, c) for c in palette)

>>> name_map['Antyczny mosiądz']
Color(red=205, green=149, blue=117, name='Antyczny mosiądz')
>>> name_map['Żółtopomarańczowy']
Color(red=255, green=174, blue=66, name='Żółtopomarańczowy')
"""

### TODO: Część z tego powinna znaleźć się w rozdziale 7

example = """Paleta GIMPa
Name: Small
Columns: 3
#
  0   0   0	Czarny
255 255 255	Biały
238  32  77	Czerwony
28 172 120	Zielony
31 117 254	Niebieski
"""

import re
from typing import TextIO
from collections.abc import Iterator


def color_GPL_r(file_obj: TextIO) -> Iterator[Color]:
    """Czytnik kolorów GPL. Uzyskaj treść z wyników uzyskania nagłówka.

    Ściśle rekurencyjne.

    >>> import io
    >>> data= io.StringIO("Paleta GIMP-a\\nNazwa: Crayola\\nKolumny: 16\\n#\\n239 222 205	Migdałowy\\n205 149 117	Antyczny mosiądz")
    >>> list( color_GPL_r(data))
    [Color(red=239, green=222, blue=205, name='Migdałowy'), Color(red=205, green=149, blue=117, name='Antyczny mosiądz')]
    """
    header_pat = re.compile(r"Paleta GIMP-a\nNazwa:\s*(.*?)\nKolumny:\s*(.*?)\n#\n", re.M)

    def read_head(file_obj: TextIO) -> tuple[TextIO, str, str, str]:
        headers = "".join(file_obj.readline() for _ in range(4))
        if match := header_pat.match(headers):
            return (
                file_obj,
                match.group(1),
                match.group(2),
                file_obj.readline().rstrip(),
            )
        else:
            raise ValueError(f"invalid {headers!r}")

    def read_tail(
        file_obj: TextIO, palette_name: str, columns: str, next_line: str
    ) -> Iterator[Color]:
        if len(next_line) == 0:
            return
        r, g, b, *name = next_line.split()
        yield Color(int(r), int(g), int(b), " ".join(name))
        yield from read_tail(
            file_obj, palette_name, columns, file_obj.readline().rstrip()
        )

    return read_tail(*read_head(file_obj))


def row_iter_gpl(file_obj: TextIO) -> tuple[str, str, Iterator[list[str]]]:
    """Czytnik kolorów GPL. Uzyskaj treść z wyników uzyskania nagłówka.

    Używa funkcji wyższego poziomu w postaci wyrażenia generatorowego. Nieco prostsze.

    >>> import io
    >>> data= io.StringIO("Paleta GIMP-a\\nNazwa: Crayola\\nKolumny: 16\\n#\\n239 222 205	Migdałowy\\n205 149 117	Antyczny mosiądz")
    >>> name, columns, colors= row_iter_gpl(data)
    >>> name
    'Crayola'
    >>> list(colors)
    [['239', '222', '205', 'Migdałowy'], ['205', '149', '117', 'Antyczny mosiądz']]

    """
    header_pat = re.compile(r"Paleta GIMP-a\nNazwa:\s*(.*?)\nKolumny:\s*(.*?)\n#\n", re.M)

    def read_head(file_obj: TextIO) -> tuple[str, str, TextIO]:
        headers = "".join(file_obj.readline() for _ in range(4))
        if match := header_pat.match(headers):
            return match.group(1), match.group(2), file_obj
        else:
            raise ValueError(f"invalid {headers!r}")

    def read_tail(
        name: str, columns: str, file_obj: TextIO
    ) -> tuple[str, str, Iterator[list[str]]]:
        return name, columns, (next_line.split() for next_line in file_obj)

    return read_tail(*read_head(file_obj))


def color_GPL_g(file_obj: TextIO) -> Iterator[Color]:
    """Czytnik kolorów GPL. Wersja z funkcją generatorową wykorzystująca
    funkcję niższego poziomu row_iter_gpl().

    >>> import io
    >>> data= io.StringIO("Paleta GIMP-a\\nNazwa: Crayola\\nKolumny: 16\\n#\\n239 222 205	Migdałowy\\n205 149 117	Antyczny mosiądz")
    >>> list(color_GPL_g(data))
    [Color(red=239, green=222, blue=205, name='Migdałowy'), Color(red=205, green=149, blue=117, name='Antyczny mosiądz')]
    """
    # pylint: disable=unused-variable
    name, columns, row_iter = row_iter_gpl(file_obj)
    return (
        Color(int(r), int(g), int(b), " ".join(name)) for r, g, b, *name in row_iter
    )


from collections.abc import Iterator


def load_colors(row_iter_gpl: tuple[str, str, Iterator[list[str]]]) -> dict[str, Color]:
    """Ładuje kolory z pliku ``crayola.gpl``, w celu zbudowania mapowania.

    >>> import io
    >>> source= io.StringIO(example)
    >>> colors= load_colors(row_iter_gpl(source))
    >>> [colors[k] for k in sorted(colors)]
    [Color(red=0, green=0, blue=0, name='Czarny'), Color(red=31, green=117, blue=254, name='Niebieski'), Color(red=28, green=172, blue=120, name='Zielony'), Color(red=238, green=32, blue=77, name='Czerwony'), Color(red=255, green=255, blue=255, name='Biały')]
    """
    # pylint: disable=unused-variable
    name, columns, row_iter = row_iter_gpl
    colors = tuple(
        Color(int(r), int(g), int(b), " ".join(name)) for r, g, b, *name in row_iter
    )
    # print( colors )
    mapping = dict((c.name, c) for c in colors)
    # print( mapping )
    return mapping


REPL_test_gpl = """
>>> import io
>>> tuple(color_GPL_r(io.StringIO(example)))
(Color(red=0, green=0, blue=0, name='Czarny'), Color(red=255, green=255, blue=255, name='Biały'), Color(red=238, green=32, blue=77, name='Czerwony'), Color(red=28, green=172, blue=120, name='Zielony'), Color(red=31, green=117, blue=254, name='Niebieski'))
>>> tuple(color_GPL_g(io.StringIO(example)))
(Color(red=0, green=0, blue=0, name='Czarny'), Color(red=255, green=255, blue=255, name='Biały'), Color(red=238, green=32, blue=77, name='Czerwony'), Color(red=28, green=172, blue=120, name='Zielony'), Color(red=31, green=117, blue=254, name='Niebieski'))
"""

### Powrót do rozdziału 3

import bisect
from collections.abc import Mapping, Iterator, Iterable, Hashable
from typing import Any


class StaticMapping(Mapping[Hashable, Any]):
    """
    >>> import io
    >>> c = StaticMapping( (c.name, c) for c in color_GPL_r(io.StringIO(example)) )
    >>> c.get("Czarny")
    Color(red=0, green=0, blue=0, name='Czarny')
    """

    def __init__(self, iterable: Iterable[tuple[Any, Any]]) -> None:
        self._data = tuple(iterable)
        self._keys = tuple(sorted(key for key, _ in self._data))

    def __getitem__(self, key: Any) -> Any:
        ix = bisect.bisect_left(self._keys, key)
        if ix != len(self._keys) and self._keys[ix] == key:
            return self._data[ix][1]
        raise ValueError("{0!r} nie znaleziono".format(key))

    def __iter__(self) -> Iterator[Any]:
        return iter(self._keys)

    def __len__(self) -> int:
        return len(self._keys)


def test_static_mapping() -> None:
    import io

    c = StaticMapping((c.name, c) for c in color_GPL_r(io.StringIO(example)))
    assert c.get("Czarny") == Color(red=0, green=0, blue=0, name="Czarny")


__test__ = {name: value for name, value in globals().items() if name.startswith("REPL")}
